#ifndef GTL_PARTICLES_HEADER_GUARD
#define GTL_PARTICLES_HEADER_GUARD

#include "gtlGeometry2D.h"
#include "gtlCompilerSpecific.h"
#include "external_libs/eastl/include/eastl/string.h"

using GTL::Geometry2D::GenericPoint2D;

template <typename T>
struct Particle;

template <typename T>
struct ParticleClass
{
  T Friction;

  uint32_t MaxLife;

  T StartAlpha, EndAlpha;
  T StartR, EndR;
  T StartG, EndG;
  T StartB, EndB;
  T StartRotation, EndRotation;

  uint32_t Type;

  ParticleClass() : Friction(1), MaxLife(0), StartAlpha(1), EndAlpha(1), StartR(1), EndR(1), StartG(1), EndG(1), StartB(1), EndB(1), StartRotation(0), EndRotation(0), Type(0), OnDeath(DEATH_DIE), OnDeathEvent(0)
  {
  }

  enum
  {
    DEATH_DIE,
    DEATH_EVENT,
    DEATH_NOTHING,
    DEATH_STOP,
    DEATH_RESTART
  };

  int OnDeath;
  typedef void(*OnDeathCallback)(Particle<T>*);
  OnDeathCallback OnDeathEvent;
};

template <typename T>
struct Particle
{
  ParticleClass<T>* Class;
  bool Alive;
  bool Zombie;

  GenericPoint2D<T> OriginalPosition;
  GenericPoint2D<T> Position;
  GenericPoint2D<T> Speed;
  GenericPoint2D<T> Gravity;

  T Alpha;
  T Rotation;
  T R, G, B;

  uint32_t Life; /// in milliseconds

  uint32_t UserData;
  eastl::string StringUserData;

  Particle()
  {
    Reset(0);
    Alive = false;
  }

  void Reset(ParticleClass<T>* pclass)
  {
    Alive = true;
    Zombie = false;

    Class = pclass;

    Alpha = R = G = B = (T)1;
    Rotation = (T)0;

    Life = 0;

    UserData = 0;
  }

  void Update(uint32_t dt)
  {
		T dtInSeconds = (T)dt / (T)1000;
    Speed += Gravity * dtInSeconds;
		if (Class->Friction != (T)1)
			Speed *= pow(Class->Friction, dtInSeconds);
    Position += Speed * dtInSeconds;

    /// TODO: Overflow life goes into next cycle in Die(dt) so that we can restart it with life already
    if (Class->MaxLife)
    {
      if (Life >= Class->MaxLife)
      {
        Life = Class->MaxLife;
        Die();
      }
      else
      {
        Life += dt;
        if (Life > Class->MaxLife) Life = Class->MaxLife;

        T percent = ((T)Life)/((T)Class->MaxLife);
        if (Class->StartAlpha != Class->EndAlpha)
          Alpha = percent * (Class->EndAlpha - Class->StartAlpha) + Class->StartAlpha;
        if (Class->StartR != Class->EndR)
          R = percent * (Class->EndR-Class->StartR) + Class->StartR;
        if (Class->StartG != Class->EndG)
          G = percent * (Class->EndG-Class->StartG) + Class->StartG;
        if (Class->StartB != Class->EndB)
          B = percent * (Class->EndB-Class->StartB) + Class->StartB;
        if (Class->StartRotation != Class->EndRotation)
          Rotation = percent * (Class->EndRotation - Class->StartRotation) + Class->StartRotation;
      }
    }
  }

  void Die()
  {
    if (Class->OnDeath == ParticleClass<T>::DEATH_DIE)
    {
      Zombie = true;
    } else if (Class->OnDeath == ParticleClass<T>::DEATH_RESTART)
    {
      Life = 0;
    } else if (Class->OnDeath == ParticleClass<T>::DEATH_STOP)
    {
      Speed = (T)0;
    } else if (Class->OnDeath == ParticleClass<T>::DEATH_EVENT)
    {
      if (Class->OnDeathEvent != NULL)
        Class->OnDeathEvent(this);
    }
  }
};

template <typename T>
struct ParticleSystem
{
  ParticleSystem()
  {
    Particles.reserve(64);
  }
  Particle<T>* AddParticle(ParticleClass<T>* pclass, const GenericPoint2D<T>& pos)
  {
    Particle<T>* p = 0;
    for (size_t i=0; i<Particles.size(); i++)
    {
      if (!Particles[i].Alive)
        p = &Particles[i];
    }
    if (p == 0)
    {
      Particles.resize(Particles.size()+1);
      p = &Particles[Particles.size()-1];
    }

    p->Reset(pclass);
    p->Position = p->OriginalPosition = pos;

    return p;
  }

  Particle<T>* AddParticleInRect(ParticleClass<T>* pclass, const GTL::Geometry2D::GenericRectangle2D<T>& rect);
  Particle<T>* AddParticleInCircle(ParticleClass<T>* pclass, const GTL::Geometry2D::GenericRectangle2D<T>& rect);

  void Update(int dt)
  {
    for (size_t i=0; i<Particles.size(); i++)
      if (Particles[i].Alive)
      {
        if (Particles[i].Zombie == true)
          Particles[i].Alive = false;
        else
          Particles[i].Update(dt);
      }
  }

  eastl::vector<Particle<T> > Particles;
};

#endif